#include "UnitTest.hpp"
#include "Trace.hpp"
#include <vector>
using std::vector;
#include <map>
using std::map;
using std::pair;
#include <string>
using std::string;
#include <stdlib.h>
#include <stdio.h>

struct FileLocation
	:public pair<string,int>
{
	FileLocation(string file, int line)
		:pair<string,int>(file,line) {}
	string toString() const
	{
		char linebuf[32];
		sprintf(linebuf, ":%i", second);
		return first+linebuf;
	}
};
static map<FileLocation, int> reportedFailures;
static map<FileLocation, string> failureDescriptions;


static int numFailedCases = 0;
static int totalFailedCases = 0;
static int numFailedTests = 0;

static void reportError(string message)
{
	numFailedCases++;
	totalFailedCases++;
	
	trace.flush();
	trace.directToStdout = true;
	trace << message;
}

int criticalFail(const char *expr, int line, const char *file)
{
	FileLocation location(file, line);
	string error = location.toString()+": Assertion '"+expr+"' failed\n";
	reportError(error);
	::abort();
	return 1;
}

int testFailed(const char *condition, int line, const char *file)
{
	FileLocation location(file, line);
	if(reportedFailures.find(location)==reportedFailures.end()) {
		reportedFailures[location] = 1;
		failureDescriptions[location] = location.toString()+": "+condition;
	} else {
		reportedFailures[location]++;
	}
	
	reportError(location.toString()+": Failed test case '"+condition+"'.\n");
	return 0;
}

int testFailed(const char *condition, int line, const char *file, string note)
{
	FileLocation location(file, line);
	if(reportedFailures.find(location)==reportedFailures.end()) {
		reportedFailures[location] = 1;
		failureDescriptions[location] = location.toString()+": "+condition;
	} else {
		reportedFailures[location]++;
	}
	
	reportError(location.toString()+": Failed test case '"+condition+"' for '"+note+"'.\n");
	return 0;
}

static bool testStarted = false;
void beginTest(string description)
{
	if(testStarted)
		endTest();
	
	testStarted = true;
	numFailedCases = 0;
	
	trace << description;
	trace << "\n";
}

void endTest()
{
	if(!testStarted)
		return;
	
	testStarted = false;
	trace << "\n\n";
	
	if(numFailedCases)
		numFailedTests++;
	
	trace.clear();
	numFailedCases = 0;
}

struct UnitTest
{
	UnitTest(const char *name, UnitTestFunc func)
		:name(name), func(func) {}
	
	string name;
	UnitTestFunc func;
};

vector<UnitTest> *testSets = NULL;

void addTestSet(UnitTestFunc func, const char *name)
{
	if(!testSets)
		testSets = new vector<UnitTest>();
	
	UnitTest test(name, func);
	testSets->push_back(test); \
}

void runTests()
{
	for(unsigned ii=0; ii<testSets->size(); ii++)
	{
		UnitTest test = (*testSets)[ii];
		
		beginTest(test.name);
		test.func();
		endTest();
	}
}

void summarizeTests()
{
	if(totalFailedCases)
	{
		std::cout << "Failed "<<totalFailedCases<<" cases in "<<numFailedTests<<" tests.\n";
		
		for(map<FileLocation, int>::iterator ii=reportedFailures.begin(); ii!=reportedFailures.end(); ii++)
		{
			FileLocation location = ii->first;
			std::cout << failureDescriptions[location] << " ("<<ii->second<<" times)\n";
		}
	}
	else
		std::cout << "All tests passed.\n";
}
